/*
 * Created on 2007-7-16
 *
 */
package jacky.lanlan.song.domain;

import jacky.lanlan.song.util.Assert;

import java.util.Date;

/**
 * 代表一个时间段。
 * <p>
 * <i style="color:red">不可变对象。</i>
 * <p>
 * 时间段包含开始和结束时间点，时间段可以无界，即开始时间 或/和 结束时间为null。<br/>
 * 当开始时间为null时，称该时间段为<i>后无界</i>，当结束时间为null时，称该时间段为<i>前无界</i>，
 * 若都为null，称该时间段为<i>全无界</i>。
 * <p>
 * 时间段是有方向的，即开始时间一定是早于结束时间的。不能构造一个开始时间晚于结束时间的时间段。
 * @author Jacky.Song
 */
public final class TimeInterval {
	private final Date startTime;
	private final Date endTime;

	/**
	 * 构造一个指定时间点的时间段。
	 * <p>
	 * 若开始时间晚于结束时间，会导致抛出IllegalArgumentException异常。
	 * @param startTime 开始时间点
	 * @param endTime 结束时间点
	 */
	public TimeInterval(Date startTime,Date endTime) {
		this.startTime=startTime;
		this.endTime=endTime;
		checkInterval(this);
	}
	
	/**
	 * 构造一个指定时间点的时间段。
	 * <p>
	 * 若开始时间晚于结束时间，会导致抛出IllegalArgumentException异常。
	 * @param startTime 开始时间点，milliseconds
	 * @param endTime 结束时间点，milliseconds
	 */
	public TimeInterval(long startTime, long endTime) {
		this.startTime=new Date(startTime);
		this.endTime=new Date(endTime);
		checkInterval(this);
	}

	private void checkInterval(TimeInterval ti) {
		if(ti.startTime!=null && ti.endTime!=null) {
			Assert.isTrue(ti.startTime.before(ti.endTime));
		}
	}
	
	public Date getEndTime() {
		if(this.endTime!=null) {
			return (Date) this.endTime.clone();//Clone 时间，防止引用泄漏导致对象可变
		}
		return null;
	}

	public Date getStartTime() {
		if(this.startTime!=null) {
			return (Date) this.startTime.clone();//Clone 时间，防止引用泄漏导致对象可变
		}
		return null;
	}
	
	/**
	 * 得到时间段长度。
	 * @return 时间段长度，若时间段无界，则返回0
	 */
	public long getInterval() {
		if(this.isForeBolderless() || this.isBackBolderless() || this.isFullBolderless()) {
			return 0;
		}
		return new Date(startTime.before(endTime)?
				endTime.getTime()-startTime.getTime():startTime.getTime()-endTime.getTime()).getTime();
	}
	
	/**
	 * 判断时间段是否是<i>前无界</i>类型。
	 */
	public boolean isForeBolderless() {
		return startTime==null && endTime!=null;
	}
	
	/**
	 * 判断时间段是否是<i>后无界</i>类型。
	 */
	public boolean isBackBolderless() {
		return startTime!=null && endTime==null;
	}
	
	/**
	 * 判断时间段是否是<i>全无界</i>类型。
	 */
	public boolean isFullBolderless() {
		return startTime==null && endTime==null;
	}
	
	/**
	 * 该时间段是否包含给定时间点。
	 * <p>
	 * 若开始时间或结束时间为null，则返回false，
	 * 否则，判断该时间段是否包含指定时间。
	 * @param time 给定的时间点
	 * @return true如果包含，false如果不包含或时间段无界
	 */
	public boolean contains(Date time) {
		if(this.isFullBolderless() || this.isForeBolderless() || this.isBackBolderless()) {
			return false;
		}
		return (time.after(startTime) || time.equals(startTime)) && 
						(time.before(endTime) || time.equals(endTime));
	}
	
	/**
	 * 该时间段是否包含给定时间点。
	 * <p>
	 * @see #contains(Date)
	 * @param time 给定的时间点
	 * @return true如果包含，false如果不包含
	 */
	public boolean contains(long time) {
		return this.contains(new Date(time));
	}
	
	/**
	 * 该时间段是否包含给定时间段。
	 * <p>
	 * @param interval 给定的时间段
	 * @return true如果包含，false如果不包含或者interval无界
	 */
	public boolean contains(TimeInterval interval) {
		if(this.startTime==null || this.endTime==null) {
			return false;
		}
		else if(interval.startTime==null|| interval.endTime==null) {
			return false;
		}
		return this.contains(interval.startTime) && this.contains(interval.endTime);
	}
	
	@Override
	protected Object clone() throws CloneNotSupportedException {
		return new TimeInterval(getStartTime(),getEndTime());//不能写this.startTime，否则会造成引用泄漏，进而破坏不可变性！
	}
	
	@Override
	public int hashCode() {
		if(this.isForeBolderless()) {
			return this.endTime.hashCode();
		}
		else if(this.isBackBolderless()) {
			return this.startTime.hashCode();
		}
		else if(this.isFullBolderless()) {
			return 0;
		}
		return ((this.startTime.hashCode() >>> 16)^(this.endTime.hashCode() >>> 32));
	}
	
	@Override
	public boolean equals(Object obj) {
		if(this==obj) {
			return true;
		}
		if(obj!=null && getClass()==obj.getClass()) {
			TimeInterval ti=(TimeInterval) obj;
			if(this.isForeBolderless() && ti.isForeBolderless()) {
				return this.endTime.equals(ti.endTime);
			}
			else if(this.isBackBolderless() && ti.isBackBolderless()) {
				return this.startTime.equals(ti.startTime);
			}
			else if(this.isFullBolderless() && ti.isFullBolderless()) {
				return true;
			}
			else if(this.startTime!=null && this.endTime!=null && ti.startTime!=null && ti.endTime!=null){
				return this.startTime.equals(ti.startTime) && this.endTime.equals(ti.endTime);
			}
		}
		return false;
	}
	
}
